package net.fruit.mq.client.impl.rocketmq;

import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.MessageClientIDSetter;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.remoting.exception.RemotingConnectException;
import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
import org.slf4j.Logger;

import net.fruit.mq.client.Message;
import net.fruit.mq.client.OnExceptionContext;
import net.fruit.mq.client.Producer;
import net.fruit.mq.client.PropertyKeyConst;
import net.fruit.mq.client.SendCallback;
import net.fruit.mq.client.SendResult;
import net.fruit.mq.client.exception.LMQClientException;
import net.fruit.mq.client.impl.tracehook.MQClientSendMessageHookImpl;
import net.fruit.mq.client.impl.util.ClientLoggerUtil;
import net.fruit.mq.client.trace.common.MQTraceConstants;
import net.fruit.mq.client.trace.dispatch.AsyncDispatcher;
import net.fruit.mq.client.trace.dispatch.impl.AsyncArrayDispatcher;

public class ProducerImpl extends MQClientAbstract implements Producer {
    private final static Logger log = ClientLoggerUtil.getClientLogger();
    private final DefaultMQProducer defaultMQProducer;

    private final AtomicBoolean started = new AtomicBoolean(false);

    private AsyncDispatcher traceDispatcher = null;

    public ProducerImpl(final Properties properties) {
        super(properties);
        this.defaultMQProducer = new DefaultMQProducer(new ClientRPCHook(sessionCredentials));

        String producerGroup = properties.getProperty(PropertyKeyConst.ProducerId, "__MQ_PRODUCER_DEFAULT_GROUP");
        this.defaultMQProducer.setProducerGroup(producerGroup);

        boolean isVipChannelEnabled = Boolean.parseBoolean(properties.getProperty(PropertyKeyConst.isVipChannelEnabled, "false"));
        this.defaultMQProducer.setVipChannelEnabled(isVipChannelEnabled);

        if (properties.containsKey(PropertyKeyConst.SendMsgTimeoutMillis)) {
            this.defaultMQProducer.setSendMsgTimeout(Integer.valueOf(properties.get(PropertyKeyConst.SendMsgTimeoutMillis).toString()));
        } else {
            this.defaultMQProducer.setSendMsgTimeout(5000);
        }

        this.defaultMQProducer.setInstanceName(this.buildIntanceName());
        this.defaultMQProducer.setNamesrvAddr(this.getNameServerAddr());
        // 消息最大大小4M
        this.defaultMQProducer.setMaxMessageSize(1024 * 1024 * 4);
        // 为Producer增加消息轨迹回发模块
        try {
            Properties tempProperties = new Properties();
            tempProperties.put(MQTraceConstants.AccessKey, sessionCredentials.getAccessKey());
            tempProperties.put(MQTraceConstants.SecretKey, sessionCredentials.getSecretKey());
            tempProperties.put(MQTraceConstants.MaxMsgSize, "128000");
            tempProperties.put(MQTraceConstants.AsyncBufferSize, "2048");
            tempProperties.put(MQTraceConstants.MaxBatchNum, "10");
            tempProperties.put(MQTraceConstants.NAMESRV_ADDR, this.getNameServerAddr());
            tempProperties.put(MQTraceConstants.InstanceName, this.buildIntanceName());
            traceDispatcher = new AsyncArrayDispatcher(tempProperties);
            traceDispatcher.start(null, this.defaultMQProducer.getInstanceName());
            this.defaultMQProducer.getDefaultMQProducerImpl().registerSendMessageHook(
                    new MQClientSendMessageHookImpl(traceDispatcher));
        } catch (Throwable e) {
            log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
        }
    }


    @Override
    public void start() {
        try {
            if (this.started.compareAndSet(false, true)) {
                this.defaultMQProducer.start();
            }
        } catch (Exception e) {
            throw new LMQClientException(e.getMessage());
        }
        if(this.traceDispatcher!=null){
            this.traceDispatcher.registerShutDownHook();
        }
    }


    @Override
    public void shutdown() {
        if (this.started.compareAndSet(true, false)) {
            this.defaultMQProducer.shutdown();
        }
    }

    @Override
    public SendResult send(Message message) {
        this.checkMQProducerServiceState(this.defaultMQProducer.getDefaultMQProducerImpl());
        org.apache.rocketmq.common.message.Message msgRMQ = RMQUtil.msgConvert(message);

        try {
            org.apache.rocketmq.client.producer.SendResult sendResultRMQ = this.defaultMQProducer.send(msgRMQ);

            message.setMsgID(sendResultRMQ.getMsgId());
            SendResult sendResult = new SendResult();
            sendResult.setTopic(sendResultRMQ.getMessageQueue().getTopic());
            sendResult.setMessageId(sendResultRMQ.getMsgId());
            return sendResult;
        } catch (Exception e) {
            log.error(String.format("Send message Exception, %s", message), e);
            throw checkProducerException(message.getTopic(), message.getMsgID(), e);
        }
    }

    @Override
    public void sendOneway(Message message) {
        this.checkMQProducerServiceState(this.defaultMQProducer.getDefaultMQProducerImpl());
        org.apache.rocketmq.common.message.Message msgRMQ = RMQUtil.msgConvert(message);
        try {
            this.defaultMQProducer.sendOneway(msgRMQ);
            message.setMsgID(MessageClientIDSetter.getUniqID(msgRMQ));
        } catch (Exception e) {
            log.error(String.format("Send message oneway Exception, %s", message), e);
            throw checkProducerException(message.getTopic(), message.getMsgID(), e);
        }
    }

    @Override
    public void sendAsync(Message message, SendCallback sendCallback) {
        this.checkMQProducerServiceState(this.defaultMQProducer.getDefaultMQProducerImpl());
        org.apache.rocketmq.common.message.Message msgRMQ = RMQUtil.msgConvert(message);
        try {
            this.defaultMQProducer.send(msgRMQ, sendCallbackConvert(message, sendCallback));
            message.setMsgID(MessageClientIDSetter.getUniqID(msgRMQ));
        } catch (Exception e) {
            log.error(String.format("Send message async Exception, %s", message), e);
            throw checkProducerException(message.getTopic(), message.getMsgID(), e);
        }
    }

    @Override
    public boolean isStarted() {
        return started.get();
    }


    @Override
    public boolean isClosed() {
        return !isStarted();
    }

    public DefaultMQProducer getDefaultMQProducer() {
        return defaultMQProducer;
    }

    private org.apache.rocketmq.client.producer.SendCallback sendCallbackConvert(final Message message, final net.fruit.mq.client.SendCallback sendCallback) {
        org.apache.rocketmq.client.producer.SendCallback RMQSendCallback = new org.apache.rocketmq.client.producer.SendCallback() {
            @Override
            public void onSuccess(org.apache.rocketmq.client.producer.SendResult sendResult) {
                sendCallback.onSuccess(sendResultConvert(sendResult));
            }

            @Override
            public void onException(Throwable e) {
                String topic = new String(message.getTopic());
                String msgId = new String(message.getMsgID());
                LMQClientException onsEx = checkProducerException(topic, msgId, e);
                OnExceptionContext context = new OnExceptionContext();
                context.setTopic(topic);
                context.setMessageId(msgId);
                context.setException(onsEx);
                sendCallback.onException(context);
            }
        };
        return RMQSendCallback;
    }

    private net.fruit.mq.client.SendResult sendResultConvert(final org.apache.rocketmq.client.producer.SendResult RMQSendResult) {
        net.fruit.mq.client.SendResult sendResult = new net.fruit.mq.client.SendResult();
        sendResult.setTopic(RMQSendResult.getMessageQueue().getTopic());
        sendResult.setMessageId(RMQSendResult.getMsgId());
        return sendResult;
    }

    private LMQClientException checkProducerException(String topic, String msgId, Throwable e) {
        if (e instanceof MQClientException) {
            //
            if (e.getCause() != null) {
                // 无法连接Broker
                if (e.getCause() instanceof RemotingConnectException) {
                    return new LMQClientException(
                            FAQ.errorMessage(String.format("Connect broker failed, Topic=%s, msgId=%s", topic, msgId), FAQ.CONNECT_BROKER_FAILED));
                }
                // 发送消息超时
                else if (e.getCause() instanceof RemotingTimeoutException) {
                    return new LMQClientException(FAQ.errorMessage(String.format("Send message to broker timeout, %dms, Topic=%s, msgId=%s",
                            this.defaultMQProducer.getSendMsgTimeout(), topic, msgId), FAQ.SEND_MSG_TO_BROKER_TIMEOUT));
                }
                // Broker返回异常
                else if (e.getCause() instanceof MQBrokerException) {
                    MQBrokerException excep = (MQBrokerException) e.getCause();
                    return new LMQClientException(FAQ.errorMessage(
                            String.format("Receive a broker exception, Topi=%s, msgId=%s, %s", topic, msgId, excep.getErrorMessage()),
                            FAQ.BROKER_RESPONSE_EXCEPTION));
                }
            }
            // 纯客户端异常
            else {
                MQClientException excep = (MQClientException) e;
                if (-1 == excep.getResponseCode()) {
                    return new LMQClientException(
                            FAQ.errorMessage(String.format("Topic does not exist, Topic=%s, msgId=%s", topic, msgId), FAQ.TOPIC_ROUTE_NOT_EXIST));
                } else if (ResponseCode.MESSAGE_ILLEGAL == excep.getResponseCode()) {
                    return new LMQClientException(
                            FAQ.errorMessage(String.format("ONS Client check message exception, Topic=%s, msgId=%s", topic, msgId),
                                    FAQ.CLIENT_CHECK_MSG_EXCEPTION));
                }
            }
        }

        return new LMQClientException("defaultMQProducer send exception", e);
    }
}

